﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	    : FI.Net Application Framework
// COMPONENT ID	    : FI.Core
// COMPONENT DESC   : FI.Net Core Foundation
//
// CREATED DATE/BY  : 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using FI.Core.Properties;
using FI.Core.Utilities;
using System;
using System.Linq;
using System.Web;
using System.Web.Caching;

namespace FI.Core.Caching
{
    /// <summary>
    /// This HttpRuntime.Cache base cache store.
    /// </summary>
    public class HttpRuntimeCacheStore : CacheStoreBase
    {
        ///<summary>
        /// Gets the priority of the cache item.
        /// </summary>
        /// <value>
        /// The priority of the cache item.
        /// </value>
        public CacheItemPriority Priority { get; private set; }

        /// <summary>
        /// Gets the expiration mode of the cahe item.
        /// </summary>
        /// <value>
        /// The expiration mode of the cahe item.
        /// </value>
        public CacheItemExpirationMode ExpirationMode { get; internal set; }

        /// <summary>
        /// Gets the expired time.
        /// </summary>
        /// <value>
        /// The expired time.
        /// </value>
        public TimeSpan ExpiredTime { get; private set; }

        /// <summary>
        /// Gets the expiration time format for expiration mode "TimeFormat".
        /// </summary>
        /// <value>
        /// The expiration time format.
        /// </value>
        public string ExpiredTimeFormat { get; private set; }

        /// <summary>
        /// Gets or sets the name of the depedent file path for expiration mode "FileDependency".
        /// </summary>
        /// <value>
        /// The name of the depedent file path.
        /// </value>
        public string DependentFileName { get; set; }

        /// <summary>
        /// Gets or set the prefix of the cache item key.
        /// </summary>
        /// <value>
        /// The prefix of the cache item key.
        /// </value>
        public string Prefix { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpRuntimeCacheStore" /> class.
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <param name="priority">The priority.</param>
        /// <param name="expirationMode">The expiration mode.</param>
        /// <param name="expiredTimeOrTimeFormatOrFileName">Name of the expired time or time format or file.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.TimeSpan.Parse(System.String)")]
        public HttpRuntimeCacheStore(string prefix, CacheItemPriority priority, CacheItemExpirationMode expirationMode, string expiredTimeOrTimeFormatOrFileName = null)
        {
            this.ValideExpirationMode(expirationMode, expiredTimeOrTimeFormatOrFileName);
            this.Prefix = string.IsNullOrEmpty(prefix) ? Guid.NewGuid().ToString() : prefix;
            this.ExpirationMode = expirationMode;
            this.Priority = priority;
            if (expirationMode == CacheItemExpirationMode.AbsoluteTime || expirationMode == CacheItemExpirationMode.SlidingTime)
            {
                this.ExpiredTime = TimeSpan.Parse(expiredTimeOrTimeFormatOrFileName);
            }
            if (expirationMode == CacheItemExpirationMode.TimeFormat)
            {
                this.ExpiredTimeFormat = expiredTimeOrTimeFormatOrFileName;
            }
            if (expirationMode == CacheItemExpirationMode.FileDependency)
            {
                this.DependentFileName = expiredTimeOrTimeFormatOrFileName;
            }
        }

        /// <summary>
        /// Gets the key of cache item to add.
        /// </summary>
        /// <param name="key">The key without prefix.</param>
        /// <returns>The key of cache item to add.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)")]
        protected virtual string GetKey(string key)
        {
            Guard.ArgumentNotNullOrEmpty(key, "key");
            return string.Format("{0}.{1}", this.Prefix, key).TrimStart('.');
        }

        /// <summary>
        /// Adds the specified value into cache.
        /// </summary>
        /// <param name="key">The key of the cache item to be added.</param>
        /// <param name="value">The value of the cache item to be added.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        protected override void AddCore(string key, object value)
        {
            Guard.ArgumentNotNullOrEmpty(key, "key");

            //HttpRuntime.Cache.Add(key,value,null,
            CacheDependency dependencies = null;
            DateTime absoluteExpiration = Cache.NoAbsoluteExpiration;
            TimeSpan slidingExpiration = Cache.NoSlidingExpiration;

            switch (this.ExpirationMode)
            {
                case CacheItemExpirationMode.AbsoluteTime:
                    {
                        absoluteExpiration = DateTime.UtcNow.Add(this.ExpiredTime);
                        break;
                    }
                case CacheItemExpirationMode.SlidingTime:
                    {
                        slidingExpiration = this.ExpiredTime;
                        break;
                    }
                case CacheItemExpirationMode.FileDependency:
                    {
                        dependencies = new CacheDependency(this.DependentFileName);
                        break;
                    }
                case CacheItemExpirationMode.NeverExpired:
                    {
                        absoluteExpiration = DateTime.MaxValue;
                        break;
                    }
            }

            HttpRuntime.Cache.Insert(this.GetKey(key), new object[] { value }, dependencies, absoluteExpiration, slidingExpiration, this.Priority.ToWebCacheItemPriority(), null);
            Keys.Add(this.Prefix, this.GetKey(key));
        }

        /// <summary>
        /// Adds or replaces an object in the cache.
        /// </summary>
        /// <param name="key">The unique value that is used to identify the object in the cache.</param>
        /// <param name="value">The object to add or replace.</param>
        protected override void PutCore(string key, object value)
        {
            object existing;
            if (this.TryGetData(this.GetKey(key), out existing))
            {
                this.Remove(key);
            }
            this.Add(key, value);
        }

        /// <summary>
        /// Remove the specified cache item.
        /// </summary>
        /// <param name="key">The key of the cache item to be deleted.</param>
        protected override void RemoveCore(string key)
        {
            Guard.ArgumentNotNullOrEmpty(key, "key");
            HttpRuntime.Cache.Remove(this.GetKey(key));
            Keys.Remove(this.Prefix, this.GetKey(key));
        }

        /// <summary>
        /// Tries the get value of the cache item based on the given key.
        /// </summary>
        /// <param name="key">The key of the cache item.</param>
        /// <param name="value">The value of the cache item.</param>
        /// <returns>
        /// A <see cref="bool" /> value indicating whether to get the value from cache store.
        /// </returns>
        protected override bool TryGetDataCore(string key, out object value)
        {
            object[] array = HttpRuntime.Cache.Get(this.GetKey(key)) as object[];
            if (array == null || array.Length == 0)
            {
                value = null;
                return false;
            }
            value = array[0];
            return true;
        }

        /// <summary>
        /// Remove all cache items added by the cache store itself.
        /// </summary>
        protected override void ClearCore()
        {
            foreach (string key in Keys.GetKeys(this.Prefix).ToArray())
            {
                HttpRuntime.Cache.Remove(key);
            }
            Keys.Clear(this.Prefix);
        }
        
        #region Private Methods
        private void ValideExpirationMode(CacheItemExpirationMode expirationMode, string expiredTimeOrTimeFormatOrFileName)
        {
            if (expirationMode != CacheItemExpirationMode.NeverExpired && expirationMode != CacheItemExpirationMode.FileDependency)
            {
                Guard.ArgumentNotNullOrEmpty(expiredTimeOrTimeFormatOrFileName, "expiredTimeOrTimeFormatOrFileName");
            }
            switch (expirationMode)
            {
                case CacheItemExpirationMode.AbsoluteTime:
                case CacheItemExpirationMode.SlidingTime:
                    {
                        TimeSpan timespan;
                        if (!TimeSpan.TryParse(expiredTimeOrTimeFormatOrFileName, out timespan))
                        {
                            throw new ArgumentException(Resources.ExceptionExpiredTimeNotValidTimeSpan, "expiredTimeOrTimeFormatOrFileName");
                        }
                        break;
                    }
                case CacheItemExpirationMode.TimeFormat:
                    {
                        TimeFormatParser.Validate(expiredTimeOrTimeFormatOrFileName);
                        break;
                    }
            }
        }
        #endregion
    }
}
